Skip to content

feat: add Ctrl-] x3 hotkey to power cycle board from serial console - #791

Open
evakhoni wants to merge 5 commits into
jumpstarter-dev:mainfrom
evakhoni:feat/console-power-hotkey
Open

feat: add Ctrl-] x3 hotkey to power cycle board from serial console#791
evakhoni wants to merge 5 commits into
jumpstarter-dev:mainfrom
evakhoni:feat/console-power-hotkey

Conversation

@evakhoni

@evakhoni evakhoni commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Add Ctrl-] x3 hotkey to the serial console that power cycles the board without exiting the session
  • Auto-discover the power driver from the DUT tree using label-based matching against known power clients (PowerClient, VirtualPowerClient)
  • Configurable via power_control_ref (explicit device name) and power_control_method (method sequence with sleep support)
  • Disables safely on ambiguity (multiple power drivers), missing ref, or invalid methods — set power_control_method to [] or null to disable explicitly
  • Reconnect the console automatically if the serial stream drops after the power cycle
  • Documented new config parameters in README and field docstrings

How it works

The root client is back-referenced onto every driver client after tree construction. PySerialClient walks the tree matching jumpstarter.dev/client labels against a known-power-clients allowlist. If exactly one match is found, it's used; multiple matches disable the hotkey with a warning. The method sequence is pre-validated at console start — invalid methods or malformed sleep: values disable the hotkey immediately rather than failing at press time. Configuration is passed from exporter to client via extra_labels().

Test plan

  • With a power driver in the DUT tree: power cycle hint appears, Ctrl-] x3 triggers a power cycle, console survives
  • Without a power driver: no hint, Ctrl-] x3 has no effect
  • Multiple power drivers: warning logged, hotkey disabled
  • power_control_ref set to explicit device: uses that device directly
  • power_control_method: null or []: hotkey disabled
  • Ctrl-B x3 exits cleanly

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds configurable power-cycle support and reconnect handling to the pyserial console. ConsoleStreamDrop signals dropped serial streams. Ctrl-] invokes an asynchronous power-cycle callback. The CLI discovers power clients, retries dropped streams, and propagates root-client references.

Changes

Serial Console Power-Cycle and Reconnect

Layer / File(s) Summary
Root client propagation
python/packages/jumpstarter/jumpstarter/client/client.py
client_from_channel sets a shared root attribute while traversing the client tree.
Power-control configuration
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver.py
PySerial adds power-control reference and method settings, normalizes string methods, and exports the settings as labels.
Console stream and input handling
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console.py
The console converts EndOfStream to ConsoleStreamDrop. Three Ctrl-B inputs exit. Three Ctrl-] inputs invoke the optional power-cycle callback.
Power discovery and reconnect loop
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py
The client discovers power controllers, validates configurable cycle steps, creates an asynchronous callback, and retries console sessions after stream drops.
Behavior validation
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py, python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py
Tests cover discovery, ambiguous and missing references, custom methods, disabled control, control-key handling, and clean console shutdown.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: mangelajo

Poem

🐇 Ctrl-] wakes the power line,
Ctrl-B ends the console time.
Dropped streams return and retry,
Root links mark the tree nearby.
The rabbit hops through every cycle.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary change: adding a Ctrl-] x3 hotkey to power cycle the board from the serial console.
Description check ✅ Passed The description directly explains the hotkey, power-driver discovery, configuration, reconnection behavior, and planned tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@LuuOW LuuOW left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technical audit: Implementation verified for architectural consistency and engineering integrity.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py`:
- Around line 135-142: The `_search_power()` method only checks the children of
the provided client node but skips checking if the client node itself is
power-capable. This causes power-driver discovery to fail when the root node has
power capabilities. Modify the method to first check if the current `client`
parameter has a `cycle` method or both `on` and `off` methods before recursing
into `client.children`, ensuring the root node is included in the power-driver
discovery process.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py`:
- Around line 17-37: The slave_file opened via os.fdopen() in the
_start_console() function is never explicitly closed, causing file descriptor
leaks across multiple test runs. Ensure proper cleanup by either returning
slave_file as part of the function's return tuple so the caller can close it
after the thread completes, or by adding a cleanup mechanism that closes
slave_file after the console.run() thread finishes executing. The key is to
guarantee that the file handle opened for slave_fd is properly closed to prevent
descriptor exhaustion.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console.py`:
- Around line 47-52: The exception re-raising in the except* ConsoleStreamDrop
and except EndOfStream blocks violates Ruff B904 linting rules by not using
explicit exception chaining. For each exception handler block that raises an
exception (the except* ConsoleStreamDrop block that raises ConsoleStreamDrop()
and the except EndOfStream block that raises ConsoleStreamDrop()), capture the
caught exception as a variable in the except clause (e.g., except*
ConsoleStreamDrop as exc) and use the from keyword when raising to provide
explicit chaining (e.g., raise ConsoleStreamDrop() from exc). Alternatively, use
make lint-fix to automatically apply the necessary fixes to satisfy B904
requirements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c042f0b-f525-40fa-9c8d-e51bfe0279df

📥 Commits

Reviewing files that changed from the base of the PR and between 24473f3 and 5118a4a.

📒 Files selected for processing (4)
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console.py
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py
  • python/packages/jumpstarter/jumpstarter/client/client.py

Comment thread python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py Outdated
@evakhoni
evakhoni force-pushed the feat/console-power-hotkey branch 3 times, most recently from 67ebd74 to beac1ee Compare June 14, 2026 18:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py (1)

8-33: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consolidate new driver-package tests into driver_test.py per repository policy.

Both files add comprehensive driver-package test coverage, but not in the required module location.

  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py#L8-L33: move/merge the power discovery and power-cycle tests into jumpstarter_driver_pyserial/driver_test.py.
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py#L40-L91: move/merge the Ctrl-B/Ctrl-] integration tests into jumpstarter_driver_pyserial/driver_test.py.

As per coding guidelines, **/jumpstarter-driver-*/jumpstarter_driver_*/*_test.py: Add comprehensive tests in driver_test.py file within the driver package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`
around lines 8 - 33, Move the power discovery and power-cycle tests
(test_find_power_client_no_root, test_find_power_client_with_cycle,
test_make_power_cycle_calls_cycle) from
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py
lines 8-33 into
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver_test.py,
and similarly move the Ctrl-B/Ctrl-] integration tests from
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py
lines 40-91 into the same driver_test.py file. Per the coding guidelines,
comprehensive driver-package tests must be consolidated in the driver_test.py
file within the driver package rather than scattered across individual module
test files.

Source: Coding guidelines

🧹 Nitpick comments (2)
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py (1)

24-33: ⚡ Quick win

Add coverage for the _make_power_cycle fallback branch.

test_make_power_cycle_calls_cycle validates only the cycle() path; the off()sleep(2)on() path in python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py is still untested.

💡 Suggested test addition
+def test_make_power_cycle_calls_off_on_when_cycle_missing():
+    called_off = threading.Event()
+    called_on = threading.Event()
+    power = MagicMock(spec=["off", "on"])
+    power.off = MagicMock(side_effect=lambda: called_off.set())
+    power.on = MagicMock(side_effect=lambda: called_on.set())
+
+    with serve(PySerial(url="loop://")) as client:
+        cycle_fn = client._make_power_cycle(power)
+        client.portal.call(cycle_fn)
+
+    assert called_off.is_set()
+    assert called_on.is_set()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`
around lines 24 - 33, The test_make_power_cycle_calls_cycle test only covers the
cycle() code path in the _make_power_cycle method, but the fallback branch that
uses off() followed by sleep(2) followed by on() remains untested. Add a new
test case that mocks the power object to trigger the fallback path (either by
not providing a cycle method or having it unavailable), then verify that the
fallback sequence of off(), sleep(), and on() method calls is executed properly
when the cycle function is invoked through client.portal.call().
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py (1)

44-45: ⚡ Quick win

Replace fixed sleeps with deterministic readiness signaling.

Line 44, Line 64, and Line 81 use time.sleep(0.1) to assume console readiness; this can cause intermittent CI flakes.

Also applies to: 64-65, 81-83

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py`
around lines 44 - 45, Replace the three instances of time.sleep(0.1) calls with
deterministic readiness signaling instead of relying on fixed delays to assume
console readiness. For each location where time.sleep(0.1) precedes an os.write
operation, implement a proper synchronization mechanism such as using select,
poll, or event-based signaling to confirm the file descriptor is actually ready
for writing before proceeding, eliminating the intermittent CI flakes caused by
timing assumptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`:
- Around line 8-33: Move the power discovery and power-cycle tests
(test_find_power_client_no_root, test_find_power_client_with_cycle,
test_make_power_cycle_calls_cycle) from
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py
lines 8-33 into
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver_test.py,
and similarly move the Ctrl-B/Ctrl-] integration tests from
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py
lines 40-91 into the same driver_test.py file. Per the coding guidelines,
comprehensive driver-package tests must be consolidated in the driver_test.py
file within the driver package rather than scattered across individual module
test files.

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`:
- Around line 24-33: The test_make_power_cycle_calls_cycle test only covers the
cycle() code path in the _make_power_cycle method, but the fallback branch that
uses off() followed by sleep(2) followed by on() remains untested. Add a new
test case that mocks the power object to trigger the fallback path (either by
not providing a cycle method or having it unavailable), then verify that the
fallback sequence of off(), sleep(), and on() method calls is executed properly
when the cycle function is invoked through client.portal.call().

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py`:
- Around line 44-45: Replace the three instances of time.sleep(0.1) calls with
deterministic readiness signaling instead of relying on fixed delays to assume
console readiness. For each location where time.sleep(0.1) precedes an os.write
operation, implement a proper synchronization mechanism such as using select,
poll, or event-based signaling to confirm the file descriptor is actually ready
for writing before proceeding, eliminating the intermittent CI flakes caused by
timing assumptions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cb30cdcc-eb35-42ef-8a00-b909cd4dc09f

📥 Commits

Reviewing files that changed from the base of the PR and between 67ebd74 and beac1ee.

📒 Files selected for processing (2)
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/console_test.py


return bytes_read, bytes_sent

def _find_power_client(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should provide a config entry to let admins specify the power device via a "ref". While I think that finding it automatically is cool, and will work out of the box in most cases, imagine environments where you have multiple power controls , and the power controls could not be what you are expecting, or the reset mechanism is different.

Even in some cases the method to be called could be "reset()", i.e. in the esp32 controller.

So I would take a config with "ref" + method in the serial config

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e.

export:
   serial:
      type:....
      config:
             ....
      reset_control:
         device:
            ref: "esp32"
         commands:
              - "reset()"
export:
   serial:
      type:....
      config:
             ....
      reset_control:
         device:
            ref: "power"
         commands:
              - "on()"
              - "sleep 2"
              - "off()"

or something like this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah config sounds good. but if unset let it fallback to the auto discovery WDYT? otherwise I guess the user base would be rather small..
+1 on the reset(), i thought I seen it somewhere but forgot it was esp32.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO auto detect continues to be risky, it could pick up the wrong power device if you had multiple doing different things in your setup. We could provide a flag for auto-detection, but explain very clearly what it does, and should be disabled by default.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even enabled by default.. but a flag :D

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's a valid concern. I think I found a middle ground: conservative auto-discovery that disables on first trouble + explicit config when needed.

Added power_control_ref and power_control_method fields — you can now specify which device and what sequence to call:

power_control_ref: "esp32"
power_control_method: ["hard_reset"]

Auto-discovery now uses a strict allowlist (only PowerClient/VirtualPowerClient labels) and disables itself if it finds multiple candidates. No flag needed since it's conservative by design.

Ready for re-review when you have time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw if needed - there's an option to disable it via the config, by setting

  - power_control_method: [] ✅
  - power_control_method: "" ✅
  - power_control_method: null ✅

@mangelajo mangelajo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is close @evakhoni :D


return bytes_read, bytes_sent

def _find_power_client(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or even enabled by default.. but a flag :D

evakhoni added a commit to evakhoni/jumpstarter that referenced this pull request Jul 31, 2026
Replace duck-typing power discovery with conservative label-based detection
to prevent false positives (GPIO pins, storage mux drivers) and handle
multi-power setups safely.

Changes:
- Add power_control_ref config field for explicit device selection
- Add power_control_method config field for custom sequences (default: ["cycle"])
- Implement allowlist: only PowerClient/VirtualPowerClient auto-discovered
- Add ambiguity detection: warn and disable when multiple candidates found
- Support method sequences: ["off", "sleep:2", "on"], ["reset"], etc.
- Support disable via empty list: power_control_method: []
- Pass config to client via extra_labels() following DbusNetwork pattern

Addresses reviewer feedback on PR jumpstarter-dev#791 about auto-discovery risks in
multi-power environments. Feature remains enabled by default for simple
setups but disables itself at first sign of trouble.

Tests: 71/71 pass, lint and type checks clean

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py (1)

13-23: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test VirtualPowerClient discovery.

This test covers PowerClient only. KNOWN_POWER_CLIENTS also accepts VirtualPowerClient. Add a parameterized case for "jumpstarter_driver_power.client.VirtualPowerClient" so a regression in that supported label is detected.

As per coding guidelines, python/**/*_test.py must “Provide comprehensive package test coverage.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`
around lines 13 - 23, Extend test_find_power_client_auto_discover to cover both
supported client labels by parameterizing the test and including
"jumpstarter_driver_power.client.VirtualPowerClient" alongside the existing
PowerClient label. Keep the discovery setup and assertion unchanged for each
parameterized case.

Source: Coding guidelines

🧹 Nitpick comments (1)
python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver.py (1)

103-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the annotation with the accepted input.

Line [103] declares power_control_method as list[str], but Lines [112-115] accept a str. A typed caller cannot pass a supported string value without a type-checking error. Use a union at the input boundary, then normalize the stored value to list[str].

Also applies to: 112-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver.py`
at line 103, Update the power_control_method annotation and initialization in
the driver configuration to accept both str and list[str] at the input boundary,
matching the handling in lines 112-115. Normalize a supplied string to a
one-element list so the stored value remains list[str], while preserving the
existing default and list behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py`:
- Around line 188-204: Update the power-control setup around _cycle to validate
every step before enabling the hotkey: require non-sleep operations on
power_client to be callable, parse each sleep: delay during construction, and
catch invalid values by logging a warning and returning None. Preserve valid
operation ordering, and add tests covering a non-callable attribute and an
invalid sleep value.

---

Outside diff comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py`:
- Around line 13-23: Extend test_find_power_client_auto_discover to cover both
supported client labels by parameterizing the test and including
"jumpstarter_driver_power.client.VirtualPowerClient" alongside the existing
PowerClient label. Keep the discovery setup and assertion unchanged for each
parameterized case.

---

Nitpick comments:
In
`@python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver.py`:
- Line 103: Update the power_control_method annotation and initialization in the
driver configuration to accept both str and list[str] at the input boundary,
matching the handling in lines 112-115. Normalize a supplied string to a
one-element list so the stored value remains list[str], while preserving the
existing default and list behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eeb0681c-6d5e-451d-b312-e5f4f45568d6

📥 Commits

Reviewing files that changed from the base of the PR and between beac1ee and ae02650.

📒 Files selected for processing (3)
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client_test.py
  • python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/driver.py

Comment thread python/packages/jumpstarter-driver-pyserial/jumpstarter_driver_pyserial/client.py Outdated
evakhoni added a commit to evakhoni/jumpstarter that referenced this pull request Jul 31, 2026
Replace duck-typing power discovery with conservative label-based detection
to prevent false positives (GPIO pins, storage mux drivers) and handle
multi-power setups safely.

Changes:
- Add power_control_ref config field for explicit device selection
- Add power_control_method config field for custom sequences (default: ["cycle"])
- Implement allowlist: only PowerClient/VirtualPowerClient auto-discovered
- Add ambiguity detection: warn and disable when multiple candidates found
- Support method sequences: ["off", "sleep:2", "on"], ["reset"], etc.
- Support disable via empty list [], empty string "", or null
- Pass config to client via extra_labels() following DbusNetwork pattern
- Pre-validate callable methods and parse sleep values at construction
- Fix type annotation: power_control_method accepts list[str] | str | None
- Add field docstrings and update README with config table and hotkey docs

Addresses reviewer feedback on PR jumpstarter-dev#791 about auto-discovery risks in
multi-power environments and CodeRabbit findings on validation timing.
Feature remains enabled by default for simple setups but disables itself
at first sign of trouble.

Tests: 74/74 pass, coverage improved from 78% to above 80% threshold

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@evakhoni
evakhoni force-pushed the feat/console-power-hotkey branch from ae02650 to ea0e41a Compare July 31, 2026 16:48
@evakhoni
evakhoni requested a review from mangelajo July 31, 2026 16:55
evakhoni and others added 5 commits July 31, 2026 21:53
- Add on_power_cycle callback to Console; Ctrl-] x3 triggers it
- Add root back-reference to DriverClient, stamped during tree construction
- Auto-discover power client from DUT siblings in PySerialClient
- Prefer cycle() when available, fall back to off()+on()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ConsoleStreamDrop exception to signal a dropped serial stream
- Add retry loop in start_console to reconnect on stream drop
- Use object.__setattr__ to stamp root back-reference without polluting pydantic schema
- Remove root field from DriverClient; it is now a dynamic attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Ctrl-B x3 exits cleanly
- Ctrl-] x3 triggers on_power_cycle and console stays alive
- Ctrl-] x3 without a power client does nothing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace duck-typing power discovery with conservative label-based detection
to prevent false positives (GPIO pins, storage mux drivers) and handle
multi-power setups safely.

Changes:
- Add power_control_ref config field for explicit device selection
- Add power_control_method config field for custom sequences (default: ["cycle"])
- Implement allowlist: only PowerClient/VirtualPowerClient auto-discovered
- Add ambiguity detection: warn and disable when multiple candidates found
- Support method sequences: ["off", "sleep:2", "on"], ["reset"], etc.
- Support disable via empty list [], empty string "", or null
- Pass config to client via extra_labels() following DbusNetwork pattern
- Pre-validate callable methods and parse sleep values at construction
- Fix type annotation: power_control_method accepts list[str] | str | None
- Add field docstrings and update README with config table and hotkey docs

Addresses reviewer feedback on PR jumpstarter-dev#791 about auto-discovery risks in
multi-power environments and CodeRabbit findings on validation timing.
Feature remains enabled by default for simple setups but disables itself
at first sign of trouble.

Tests: 74/74 pass, coverage improved from 78% to above 80% threshold

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixes two power auto-discovery bugs:

1. **Proxy dedup**: When a Proxy driver delegates to a power client
   (e.g., Proxy(ref="qemu.power")), the same driver instance appears
   twice in the client tree — once via proxy, once via direct parent.
   This triggered false "Multiple power drivers found" ambiguity.
   Fixed by tracking UUIDs in `_collect_power_clients` to skip
   already-seen drivers.

2. **Allowlist expansion**: RideSXPowerClient, NoyitoPowerClient, and
   SNMPServerClient all extend PowerClient but weren't in the allowlist,
   causing auto-discovery to silently find nothing on those targets.
   Added all three to `KNOWN_POWER_CLIENTS`.

Also adds user feedback: when Ctrl-] x3 is pressed without a power
driver, log a warning instead of silently doing nothing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@evakhoni
evakhoni force-pushed the feat/console-power-hotkey branch from ea0e41a to f807a7f Compare July 31, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants